package com.paolocorno.datatable;

import com.googlecode.xmlzen.XmlSlicer;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

/**
 *
 * @author: Paolo Corno
 */

@DataTableRecordAnnotation(xmlTag = "RECORD")
public abstract class AbstractDataTableRecord implements Comparable {

    /**
     *
     */
    public AbstractDataTableRecord() {
    }

    /**
     *
     * @param name
     * @return
     */
    public int getFieldPos(String name) {
        int ret = -1;

        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Annotation aa = declaredFields[i].getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                if (((DataTableFieldAnnotation) aa).name().compareTo(name) == 0) {
                    ret = ((DataTableFieldAnnotation) aa).position();
                }
            }
        }
        return ret;
    }

    /**
     *
     * @param position
     * @return
     */
    public String getFieldName(int position) {
        String ret = null;

        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Annotation aa = declaredFields[i].getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                if (((DataTableFieldAnnotation) aa).position() == position) {
                    ret = ((DataTableFieldAnnotation) aa).name();
                }
            }
        }
        return ret;
    }

    /**
     *
     * @param position
     * @return
     */
    public boolean isReadonly(int position) {
        boolean ret = false;

        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Annotation aa = declaredFields[i].getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                if (((DataTableFieldAnnotation) aa).position() == position) {
                    ret = ((DataTableFieldAnnotation) aa).readonly();
                }
            }
        }
        return ret;
    }

    /**
     *
     * @return
     */
    public int getRecordFieldsCount() {
        int ret = 0;

        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            if (declaredFields[i].getAnnotation(DataTableFieldAnnotation.class) != null) {
                ret++;
            }
        }
        return ret;
    }

    /**
     *
     * @return
     */
    public String[] getRecordFieldNames() {
        int inum = getRecordFieldsCount();
        String[] ret = new String[inum];
        Field[] declaredFields = this.getClass().getDeclaredFields();
        int index = 0;
        for (int i = 0; i < declaredFields.length; i++) {
            Annotation aa = declaredFields[i].getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                ret[index] = ((DataTableFieldAnnotation) aa).name();
                index++;
            }
        }
        return ret;
    }

    /**
     *
     * @param name
     * @param value
     * @return
     */
    public boolean setFieldValue(String name, Object value) {
        boolean ret = false;

        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Annotation aa = declaredFields[i].getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                if (((DataTableFieldAnnotation) aa).name().compareTo(name) == 0) {
                    ret = setField(declaredFields[i], value.toString());
                }
            }
        }
        return ret;
    }

    /**
     *
     * @param position
     * @param value
     * @return
     */
    public boolean setFieldValue(int position, Object value) {
        boolean ret = false;


        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Annotation aa = declaredFields[i].getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                if (((DataTableFieldAnnotation) aa).position() == position) {
                    ret = setField(declaredFields[i], value.toString());
                }
            }
        }
        return ret;
    }

    /**
     *
     * @param name
     * @return
     */
    public Class getFieldType(String name) {
        Class ret = null;

        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Annotation aa = declaredFields[i].getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                if (((DataTableFieldAnnotation) aa).name().compareTo(name) == 0) {
                    return declaredFields[i].getType();
                }
            }
        }
        return ret;
    }

    /**
     *
     * @param position
     * @return
     */
    public Class getFieldType(int position) {
        Class ret = null;

        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Annotation aa = declaredFields[i].getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                if (((DataTableFieldAnnotation) aa).position() == position) {
                    return declaredFields[i].getType();
                }
            }
        }
        return ret;
    }

    /**
     *
     * @param name
     * @return
     */
    public Object getFieldValue(String name) {
        Object ret = null;

        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Annotation aa = declaredFields[i].getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                if (((DataTableFieldAnnotation) aa).name().compareTo(name) == 0) {
                    try {
                        return declaredFields[i].get(this);
                    } catch (Exception ex) {
                        return null;
                    }
                }
            }
        }
        return ret;
    }

    /**
     *
     * @param position
     * @return
     */
    public Object getFieldValue(int position) {
        Object ret = null;

        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Annotation aa = declaredFields[i].getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                if (((DataTableFieldAnnotation) aa).position() == position) {
                    try {
                        return declaredFields[i].get(this);
                    } catch (Exception ex) {
                        return null;
                    }
                }
            }
        }
        return ret;
    }

    /**
     *
     * @param name
     * @return
     */
    public String getXmlTag(String name) {
        String ret = null;

        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Annotation aa = declaredFields[i].getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                if (((DataTableFieldAnnotation) aa).name().compareTo(name) == 0) {
                    return ((DataTableFieldAnnotation) aa).xmlTag();
                }
            }
        }
        return ret;
    }

    /**
     *
     */
    public void printTypes() {
        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Annotation aa = declaredFields[i].getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                System.out.println(declaredFields[i].getGenericType().toString());
            }
        }
    }

    /**
     *
     * @param describeFields
     * @return
     */
    public String toXml(boolean describeFields) {
        String ret = "";
        Annotation aa;

        Field[] declaredFields = this.getClass().getDeclaredFields();
        for (int i = 0; i < declaredFields.length; i++) {
            Field field = declaredFields[i];
            aa = field.getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                Type type = field.getGenericType();
                DataTableFieldAnnotation fa = (DataTableFieldAnnotation) aa;
                ret += "<" + fa.xmlTag();
                if (describeFields == true) {
                    ret += " name=\"" + fa.name() + "\""
                            + " type=\"" + type.toString() + "\"";
                }
                ret += ">";
                try {
                    Object value = field.get(this);
                    if (value == null) {
                        ret += "null";
                    } else {
                        if (fa.format().isEmpty()) {
                            ret += value.toString();
                        } else {
                            ret += String.format(fa.format(), value);
                        }
                    }
                } catch (IllegalArgumentException ex) {
                    ret += "";
                } catch (IllegalAccessException ex) {
                    ret += "";
                }

                ret += "</" + fa.xmlTag() + ">";
            }
        }
        return ret;
    }

    /**
     *
     * @param input
     * @return
     */
    public boolean fromXml(String input) {
        boolean ret = false;
        Field[] declaredFields = this.getClass().getDeclaredFields();
        Annotation aa;
        for (int i = 0; i < declaredFields.length; i++) {
            Field field = declaredFields[i];
            aa = field.getAnnotation(DataTableFieldAnnotation.class);
            if (aa != null) {
                String tag = ((DataTableFieldAnnotation) aa).xmlTag();
                XmlSlicer slicer = XmlSlicer.cut(input);
                String value = XmlSlicer.cut(input).get(tag).toString();
                ret = setField(field, value);
            }
        }
        return ret;
    }

    private boolean setField(Field field, String value) {
        boolean ret = false;
        Class clz = field.getType();
        try {
            if (clz == String.class) {
                field.set(this, value);
            } else if (clz == Integer.class || clz == Long.class || clz == Float.class
                    || clz == Double.class || clz == Boolean.class || clz == Byte.class) {
                Method method = clz.getMethod("valueOf", String.class);
                Object obj = method.invoke(this, value);
                field.set(this, obj);
                ret = true;
            }
        } catch (Exception ex) {
            try {
                field.set(this, null);
            } catch (IllegalArgumentException ex1) {
                ret = false;
            } catch (IllegalAccessException ex1) {
                ret = false;
            }
        }
        return ret;
    }

    @Override
    public int compareTo(Object o) {
        return compareTo(o, 0);
    }

    /**
     *
     * @param o the DataTableRecord object to compare to
     * @param fieldName the name of the field in DataTableRecord to be used for comparison
     * @return
     */
    public int compareTo(Object o, String fieldName) {
        return compareTo(o, this.getFieldPos(fieldName));
    }

    /**
     *
     * @param o
     * @param fieldIndex
     * @return
     */
    public int compareTo(Object o, int fieldIndex) {
        int ret = Integer.MAX_VALUE;
        if (o.getClass() == this.getClass()) {
            Object objSource = this.getFieldValue(fieldIndex);
            Object objOther = ((AbstractDataTableRecord) o).getFieldValue(fieldIndex);
            try {
                Method sourceCompare = objSource.getClass().getDeclaredMethod("compareTo", new Class[]{Object.class});

                if (sourceCompare != null) {
                    Object[] args = {objOther};
                    Object retObj = sourceCompare.invoke(objSource, args);
                    ret = ((Integer) retObj).intValue();
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return ret;
    }

    /**
     *
     * @param fieldName
     * @return
     */
    public static DataTableRecordComparator createRecordComparator(String fieldName) {
        DataTableRecordComparator rc = new DataTableRecordComparator(fieldName);
        return rc;
    }

    /**
     *
     * @param fieldIndex
     * @return
     */
    public static DataTableRecordComparator createRecordComparator(int fieldIndex) {
        DataTableRecordComparator rc = new DataTableRecordComparator(fieldIndex);
        return rc;
    }
}
